const PENDING = "pedding";
const FulFIILED = "fulfilled";
const REJECTED = "rejected";

class Promise {
    static accept = "must accept a function as a parameter";

    status = PENDING;

    value = "";

    reason = "";

    successCallback = [];

    failCallback = [];

    constructor(handler) {
        if ("function" === typeof handler) {
            try {
                handler(this.resolve, this.reject);
            } catch (error) {
                this.reject(error);
            }
        } else {
            throw new TypeError(this.accept);
        }
    }


    resolve = (value) => {
        if (this.status === PENDING) {
            this.status = FulFIILED;
            this.value = value;
            while (this.successCallback.length) {
                this.successCallback.shift()();
            }
        }
    }

    reject = (reason) => {
        if (this.status === PENDING) {
            this.status = REJECTED;
            this.reason = reason;
            while (this.failCallback.length) {
                this.failCallback.shift()();
            }
        }
    }


    /**
     * 函数中使用数据:
     * this.value
     * this.reason
     * 在同步调用时，在传入之前就已赋值，即在之前调用resolve函数进行赋值操作;
     * 在异步调用时，在传入之前尚未赋值，因尚未调用resolve函数进行赋值操作，
     * 即在resolve函数调用successCallback函数前一步进行赋值操作，也是使用箭头函数的关键原因;
     * 使用setTimeout(()=>{}, 0)的关键原因:在于获取Promise实例并在middleware函数中使用
     * @param {function} successCallback 
     * @param {function} failCallback 
     */
    then = (successCallback, failCallback) => {
        successCallback = successCallback ? successCallback : value => value;
        const prosime = new Promise((resolve, reject) => {
            failCallback = failCallback ? failCallback : reason => {
                try {
                    throw reason;
                } catch (error) {
                    reject(error)
                }
            };

            setTimeout(() => {
                try {
                    if (this.status === FulFIILED) {
                        middleware(successCallback(this.value), resolve, reject, prosime);
                    } else if (this.status === REJECTED) {
                        middleware(failCallback(this.reason), resolve, reject, prosime);
                    } else {
                        this.successCallback.push(() => {
                            middleware(successCallback(this.value), resolve, reject, prosime);
                        });
                        this.failCallback.push(() => {
                            middleware(failCallback(this.reason), resolve, reject, prosime);
                        });
                    }
                } catch (error) {
                    reject(error);
                }
            }, 0);
        });

        return prosime;
    }

    catch = (failCallback) => {
        return this.then(undefined, failCallback);
    }

    /**
     * 
     * @param {array} array 
     */
    static all(array) {
        let result = [], sum = 0;
        const middleware = (key, value, reslove) => {
            result[key] = value;
            sum++
            if (sum === array.length) {
                reslove(result);
            }
        }
        return new Promise((reslove, reject) => {

            for (const i of Object.keys(array || [])) {
                let instance = array[i];
                if (instance instanceof Promise) {
                    instance.then(value => middleware(i, value, reslove), reason => reject(reason));
                } else {
                    middleware(i, instance, reslove);
                }
            }
        });
    }

    static resolve(value) {
        if (value instanceof Promise) return value;
        return new Promise((resolve, reject) => resolve(value));
    }

    finally(callback) {
        return this.then(() => {
            return Promise.resolve(callback()).then(value => value);
        }, () => {
            return Promise.resolve(callback()).then(reason => { throw reason });
        });
    }

}

const middleware = (instance, resolve, reject, prosime) => {
    if (prosime === instance) {
        return reject(new TypeError("Chaining cycle detected for promise #<Promise>"));
    }
    if (instance instanceof Promise) {
        instance.then(resolve, reject);
    } else {
        resolve(instance);
    }
}



function e2e_1() {
    const promise = new Promise((resolve, reject) => {
        // setTimeout(() => {
        //     resolve(99);
        // }, 2000);
        resolve(99);
    });


    let instance = promise.then(val => {
        console.log(`middleware: ${val}`);
        return 1 + val;
        // return instance;
        return Promise.resolve(18);
    });

    instance.then(val => {
        console.log(`End val: ${val}`);
    }, reason => {
        console.log(reason);
    });
}


function e2e_2() {
    Promise.all([
        "a",
        3,
        new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(99);
                // reject("#####1");
            }, 2500);
        }),
        "#9x3",
        78,
        new Promise((resolve, reject) => {
            // reject("#####1");
            resolve(99);
        })
    ]).then(vals => {
        console.log(vals);
    }, reason => {
        console.log(reason);
    });
}


function e2e_3() {
    const promise = new Promise((resolve, reject) => {
        // setTimeout(() => {
        //     resolve(99);
        // }, 2000);
        console.log(99);

        resolve(99);
    });

    promise.then(vals => {
        setTimeout(() => {
            vals = vals + 1;
            console.log(vals);
            Promise.resolve(vals);
        }, 3000);
    }).finally(() => {
        console.log("this is finally");
    });
}


function e2e_4() {
    const promise = new Promise((resolve, reject) => {
        // setTimeout(() => {
        //     reject("失败");
        // }, 2000);
        reject("#x000006");
    });

    promise
        .then(vals => {
            console.log(`then`, vals);
            // return Promise.resolve(vals);
        })
        .then(undefined, undefined)
        .catch((reason) => {
            console.log(`catch:`, reason);
        });
}


e2e_4();